package com.lmaos.basetools.proxy.autoproxy;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtMethod;
import javassist.CtNewConstructor;

import com.lmaos.basetools.proxy.BeanProxy;
import com.lmaos.basetools.proxy.MethodIntercept;

public class BeanAutoProxy implements BeanProxy{
	Map<String, ClassBean> classCaches = new ConcurrentHashMap<String, ClassBean>();
	MethodIntercept intercept;
	private static String methodInterceptClassName = "com.lmaos.basetools.proxy.MethodIntercept";
	private static String methodInterceptFieldName = "methodIntercept";
	@Override
	public Object getBean(String className) {
		return getBean(className,null);
	}
	
	public Object getBean(String className,Object obj) {
		ClassBean classBean = classCaches.get(className);
		if(classBean == null){
			return null;
		}
		
		Class clazz = classBean.getClazz();
		MethodIntercept intercept = classBean.getMethodIntercept();
		if(intercept == null){
			intercept = this.intercept;
		}
		try {
			obj = obj==null?createObj(className):obj;
			Object bean = clazz.newInstance();
			setValue(bean, methodInterceptFieldName, intercept);
			setValue(bean, "obj", obj);
			return bean;
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
		
	}
	
	@Override
	public <T> T getBean(Class<T> clazz) {
		Object result = getBean(clazz.getName());
		if(result == null){
			return null;
		}
		return (T) result;
	}
	
	@Override
	public void addBean(String className) {
		
		if(classCaches.containsKey(className)){
			return;
		}
		try {
			ClassPool cp = ClassPool.getDefault();
			CtClass tclass = cp.get(className);
			tclass.setModifiers(Modifier.PUBLIC);
			CtMethod[] tMethods = tclass.getDeclaredMethods();
			CtClass subclass = cp.makeClass(className+"_LMAOS_AUTO_PROXY");// 新建类
			subclass.setSuperclass(tclass);
			subclass.addConstructor(CtNewConstructor.defaultConstructor(subclass));
			CtField methodIntercept = new CtField(cp.getCtClass(methodInterceptClassName), methodInterceptFieldName, subclass);
			subclass.addField(methodIntercept);
			CtField superObj = new CtField(tclass, "obj", subclass);
			subclass.addField(superObj);
			// 重写类方法
			for (CtMethod m : tMethods) {
				if(Modifier.isStatic(m.getModifiers())){
					continue;
				}
				CtClass[] paramTypes = m.getParameterTypes();
				CtClass returnType = m.getReturnType();
				CtMethod subMethod = new CtMethod(m.getReturnType(), m.getName(), paramTypes, subclass);
				subclass.setModifiers(Modifier.PUBLIC);
				subMethod.setExceptionTypes(new CtClass[]{cp.get("java.lang.Throwable")});
				StringBuilder body = new StringBuilder();
				body.append("{");
//				body.append("if(").append(methodInterceptFieldName).append("==null)return null;\n");
				body.append("Class[] paramTypes =").append(getParamTypes(paramTypes)).append(";\n");
				body.append("Object[] params = $args;\n");
				body.append("Object  obj = this.obj;\n");
				body.append("String mname = \"").append(m.getName()).append("\";\n");
				body.append("java.lang.reflect.Method  method = obj.getClass().getDeclaredMethod(mname, paramTypes);\n");
				body.append("Object result = ").append(methodInterceptFieldName).append(".").append("invoke(obj,method,params);\n");
				body.append(return_result(returnType));
//				body.append("return result;");
				body.append("}");
//				CtMethod subMethod = CtNewMethod.make(m.getReturnType(), m.getName(), m.getParameterTypes(), new CtClass[]{cp.get("java.lang.Throwable")}, body.toString(), subclass);
//				System.out.println(body);
				subMethod.setBody(body.toString());
				subclass.addMethod(subMethod);
			}
			Class clazz = subclass.toClass();
			
			ClassBean classBean = new ClassBean(clazz);
			classCaches.put(className, classBean);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@Override
	public void addBean(Class clazz) {

		addBean(clazz.getName());
	}

	@Override
	public void setMethodInterceot(MethodIntercept intercept) {
	
		this.intercept = intercept;
	}

	@Override
	public void addMethodInterceot(Class clazz, MethodIntercept intercept) {
		if(intercept == null)return;
		ClassBean classBean = classCaches.get(clazz.getName());
		classBean.setMethodIntercept(intercept);
	}
	private String getParamTypes(CtClass[] paramTypes){
		if(paramTypes == null||paramTypes.length==0){
			return null;
		}
		StringBuilder types = new StringBuilder();
		types.append("{");
		for (CtClass ctClass : paramTypes) {
			types.append(ctClass.getName()).append(".class,");
		}
		types.replace(types.length()-1, types.length(), "}");
		return types.toString();
	}
	
	private void setValue(Object obj,String fieldName,Object value){
		try {
			Field field = obj.getClass().getDeclaredField(fieldName);
			field.setAccessible(true);
			field.set(obj, value);
		} catch (Exception e) {
		}
	}
	private Object createObj(String className) throws InstantiationException, IllegalAccessException, ClassNotFoundException{
		return createObj(Class.forName(className));
	}
	private Object createObj(Class clazz) throws InstantiationException, IllegalAccessException{
		return clazz.newInstance();
	}
	public String return_result(CtClass returnType) throws Exception {
		ClassPool cp = ClassPool.getDefault();
			if(returnType == CtClass.voidType){
				return "";
			}
			else if(returnType == CtClass.intType){
				return "return ((Number)result).intValue();";
			}else if(returnType == CtClass.longType){
				return "return ((Number)result).longValue();";
			}else if(returnType == CtClass.doubleType){
				return "return ((Number)result).doubleValue();";
			}else if(returnType == CtClass.floatType){
				return "return ((Number)result).floatValue();";
			}else if(returnType == CtClass.shortType){
				return "return ((Number)result).shortValue();";
			}else if(returnType == CtClass.byteType){
				return "return ((Number)result).byteValue();";
			}else if(returnType == CtClass.charType){
				return "return com.lmaos.basetools.proxy.autoproxy.BeanAutoProxy.getChar(result);";
			}else if(returnType == CtClass.booleanType){
				return "return com.lmaos.basetools.proxy.autoproxy.BeanAutoProxy.getBoolean(result);";
			}else if(returnType == cp.get("java.lang.Integer")){
				return "return com.lmaos.basetools.proxy.autoproxy.BeanAutoProxy.getInteger(result);";
			}else if(returnType == cp.get("java.lang.Long")){
				return "return com.lmaos.basetools.proxy.autoproxy.BeanAutoProxy.getLong(result);";
			}else if(returnType == cp.get("java.lang.Double")){
				return "return com.lmaos.basetools.proxy.autoproxy.BeanAutoProxy.getDouble(result);";
			}else if(returnType == cp.get("java.lang.Float")){
				return "return com.lmaos.basetools.proxy.autoproxy.BeanAutoProxy.getFloat(result);";
			}else if(returnType == cp.get("java.lang.Short")){
				return "return com.lmaos.basetools.proxy.autoproxy.BeanAutoProxy.getShort(result);";
			}else if(returnType == cp.get("java.lang.Byte")){
				return "return com.lmaos.basetools.proxy.autoproxy.BeanAutoProxy.getByte(result);";
			}else if(returnType == cp.get("java.lang.Character")){
				return "return com.lmaos.basetools.proxy.autoproxy.BeanAutoProxy.getCharacter(result);";
			}else if(returnType == cp.get("java.lang.Boolean")){
				return "return com.lmaos.basetools.proxy.autoproxy.BeanAutoProxy.getXBoolean(result);";
			}else if(returnType == cp.get("java.lang.Object")){
				return "return result;";
			}
		
		return "return ("+returnType.getName()+")result;";
	}
	
	public static Integer getInteger(Object result){
		return ((Number) result).intValue();
	}
	public static Long getLong(Object result){
		return ((Number) result).longValue();
	}
	public static Double getDouble(Object result){
		return ((Number) result).doubleValue();
	}
	public static Float getFloat(Object result){
		return ((Number) result).floatValue();
	}
	public static Byte getByte(Object result){
		return ((Number) result).byteValue();
	}
	public static Short getShort(Object result){
		return ((Number) result).shortValue();
	}
	public static Character getCharacter(Object result){
		return (Character) result;
	}
	public static char getChar(Object result){
		return (Character) result;
	}
	public static Boolean getXBoolean(Object result){
		return (Boolean) result;
	}
	public static boolean getBoolean(Object result){
		return (Boolean) result;
	}
}
